/**
 * 
 */
package view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import javax.swing.text.JTextComponent;

import com.alee.laf.scroll.WebScrollPane;
import com.alee.laf.text.WebTextField;

import model.Recipe;
import model.RecipeIngredient;
import model.RecipeStep;

import controller.Controls;

/**
 * 
 *
 */
public class AddRecipePanel2 extends JPanel implements TableModelListener {
    /**
     * Serial Version ID;
     */
    private static final long serialVersionUID = 5946346702489097949L;
    
    private static final String TITLE = "Title ";
    private static final String AUTHOR = "Author ";
    private static final String PREP = "Prep Time (min) ";
    private static final String COOK = "Cook Time (min) ";
    private static final String SERVE = "Serving Size ";
    private static final String[] LABELS = {TITLE, AUTHOR, PREP, COOK, SERVE};
    private static final String[] DROP_LABELS = {"Course", "Cuisine", "Level"};
    private static final String[] INGRED_LABELS = {"Quantity (Decimal)", "Unit of Measure", "Ingredient Name", "Style of Prep"};
    private static final String[] MEASURE_ABB = {"tsp.", "tbl.", "fl oz", "c", "pt",
        "qt", "gal", "ml", "l", "lb", "oz", "mg", "g", "kg", "mm", "cm", "m", "in",
        "meter", "inch"};
    private static final String[] MEASUREMENTS = {"teaspoon", "tablespoon", "fluid ounce", "cup", "pint",
        "quart", "gallon", "milliliter", "liter", "pound", "ounce", "milligram", "gram", "kilogram", "millimeter", "centimeter",
        "meter", "inch", "whole"};
    private static final String COURSE_SELECT = "Main Dish";
    private static final String CUISINE_SELECT = "American";
    private static final String LEVEL_SELECT = "Easy";
    //Dimensions
    private static final Dimension DIRECTION_SIZE = new Dimension(400, 150);
    private static final Dimension DIRECT_VPORT_SIZE = new Dimension(475, 400);
    private static final Dimension INGRED_TABLE_SIZE = new Dimension(450, 80);
    private static final Dimension DROP_PANEL_SIZE = new Dimension(300, 45);
    /** Maps all labels to a corresponding text field.*/
    private Map<String, JTextComponent> my_textMap;
    /** Maps all labels to a corresponding warning label.*/
    private Map<String, JLabel> my_warnMap;
    private Map<String, String> my_unitMap;
    /** Button for submitting registration data.*/
    private final Controls my_controller;
    private JComboBox<String> my_courseBox;
    private JComboBox<String> my_cuisineBox;
    private JComboBox<String> my_difficultyBox;
    private JTable my_table;
    private JTextArea my_directionArea;
    private JLabel my_titleLabel;
    private JButton my_submitButton;
    private JButton my_editButton;
    private Set<String> my_measurements;
    private Recipe my_recipe;
    private boolean my_validFlag;
    private WebScrollPane my_areaScroll;
    
    public AddRecipePanel2(Controls the_controller) {
        my_controller = the_controller;
        my_titleLabel = new JLabel("Add Recipe");
        my_titleLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 20));
        my_titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
        my_measurements = new HashSet<String>(Arrays.asList(MEASUREMENTS));
        my_validFlag = true;
        JPanel submitButtonHolder = makeSubmitHolder();
        this.setLayout(new BorderLayout());
        this.setOpaque(false);
        createTextMap();
        buildMainPanel();
        this.add(submitButtonHolder, BorderLayout.SOUTH);
    }
    public AddRecipePanel2(Controls the_controller, Recipe the_recipe) {
        my_controller = the_controller;
        my_recipe = the_recipe;
        my_titleLabel = new JLabel("Edit Recipe");
        my_titleLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 20));
        my_titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
        my_measurements = new HashSet<String>(Arrays.asList(MEASUREMENTS));
        my_validFlag = true;
        this.setLayout(new BorderLayout());
        this.setOpaque(false);
        createUnitMap();
        createTextMap();
        buildMainPanel();
        setupEditRecipe();      
    }
    private void createUnitMap() {
        my_unitMap = new HashMap<String, String>();
        for (int i = 0; i < MEASUREMENTS.length; i++) {
            my_unitMap.put(MEASURE_ABB[i], MEASUREMENTS[i]);
        }
    }
    private void buildMainPanel() {
        JPanel masterPanel = new JPanel();
        masterPanel.setLayout(new BoxLayout(masterPanel, BoxLayout.PAGE_AXIS));
        //Panels
        JPanel titlePanel = buildTitlePanel();
        JPanel dropPanel = buildDropPanel();
        JPanel ingredientPanel = buildIngredientPanel();
        JPanel directionPanel = buildDirectionPanel();
        //Layouts

        //Alignment
        titlePanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        dropPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        ingredientPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        directionPanel.setAlignmentX(Component.LEFT_ALIGNMENT);

        //Opacity
        titlePanel.setOpaque(false);
        dropPanel.setOpaque(false);
        ingredientPanel.setOpaque(false);
        directionPanel.setOpaque(false);
        masterPanel.setOpaque(false);
        
        //Final Construction
        masterPanel.add(titlePanel);
        masterPanel.add(dropPanel);
        masterPanel.add(ingredientPanel);
        masterPanel.add(directionPanel);
        masterPanel.setBorder(BorderFactory.createEmptyBorder(15, 50, 15, 50));
        //masterPanel.add(my_submitButton);
        JPanel masterHolder = new JPanel();
        masterHolder.add(masterPanel);
        masterHolder.setOpaque(false);
        this.add(masterPanel, BorderLayout.CENTER);
        this.add(my_titleLabel, BorderLayout.NORTH);
        
    }
    private void setupEditRecipe() {
        my_textMap.get(TITLE).setText(my_recipe.getTitle());
        my_textMap.get(AUTHOR).setText(my_recipe.getAuthor());
        my_textMap.get(PREP).setText(Integer.toString(my_recipe.getPrepTime()));
        my_textMap.get(COOK).setText(Integer.toString(my_recipe.getCookTime()));
        my_textMap.get(SERVE).setText(Integer.toString(my_recipe.getServes()));
        //my_courseBox.setSelectedItem(my_recipe.getCourse());
        //my_cuisineBox.setSelectedItem(my_recipe.getCuisine());
        my_difficultyBox.setSelectedItem(my_recipe.getDifficulty());
        List<RecipeIngredient> ingredList = my_recipe.getIngredients();
        for (int i = 0; i < ingredList.size(); i++) {
            final Double quantity = ingredList.get(i).getQuantity();
            final String prepStyle = ingredList.get(i).getPrepStyle();
            if (quantity != null && quantity > 0) {
                my_table.setValueAt(Double.toString(ingredList.get(i).getQuantity()), i, 0);
            }
            my_table.setValueAt(my_unitMap.get(ingredList.get(i).getMeasureUnit()), i, 1);
            
            my_table.setValueAt(ingredList.get(i).getIngredient(), i, 2);
            my_table.setValueAt(prepStyle, i, 3);

        }
        List<RecipeStep> stepList = my_recipe.getSteps();
        String directions = "";
        for (int i = 0; i < stepList.size(); i++) {
            directions += stepList.get(i).getDirections() + "\n\n";
        }
        my_directionArea.setText(directions);
        //sets scroll bar to top.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() { 
                my_areaScroll.getVerticalScrollBar().setValue(0);
            }
         });
        //Add Edit Button
        addEditButton();
    }
    private JPanel buildTitlePanel() {
        JPanel result = new JPanel();
        result.setLayout(new BoxLayout(result, BoxLayout.PAGE_AXIS));

        //final construction
        for (int i = 0; i < LABELS.length; i++) {
            JPanel panel = new JPanel();
            panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
            panel.setAlignmentX(Component.LEFT_ALIGNMENT);
            panel.setOpaque(false);
            JLabel label = new JLabel(LABELS[i], JLabel.RIGHT);
            if (i == 0) {
                label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0 , 18));
            } else if (i == 1) {
                label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4));
            } else if (i == 2) {
                label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2));
            } else if (i == 4) {
                label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 22));
            }
            panel.add(label);
            panel.add(my_textMap.get(LABELS[i]));
            panel.add(my_warnMap.get(LABELS[i]));
            
            result.add(panel);
        }
        
        return result;
    }
    private JPanel buildDropPanel() {
        JPanel dropPanel = new JPanel();
        GridLayout grid = new GridLayout(2, 3);
        grid.setVgap(10);
        grid.setHgap(3);
        dropPanel.setLayout(grid);
        //dropPanel.setPreferredSize(dropPanel.getPreferredSize());
        for (int i = 0; i < DROP_LABELS.length; i++) {
            JLabel label = new JLabel(DROP_LABELS[i], JLabel.CENTER);
            //label.setPreferredSize(new Dimension(100, 20));
            label.setOpaque(false);
            dropPanel.add(label);
        }
        //Dropdown menus
        my_courseBox = new JComboBox(my_controller.getCourses().toArray());
        my_cuisineBox = new JComboBox(my_controller.getCuisines().toArray());
        my_difficultyBox = new JComboBox(my_controller.getDifficulty().toArray());
        //Add menus
        my_courseBox.setSelectedItem(COURSE_SELECT);
        my_cuisineBox.setSelectedItem(CUISINE_SELECT);
        my_difficultyBox.setSelectedItem(LEVEL_SELECT);
        dropPanel.add(my_courseBox);
        dropPanel.add(my_cuisineBox);
        dropPanel.add(my_difficultyBox);
        //Opacity
        my_courseBox.setOpaque(false);
        my_cuisineBox.setOpaque(false);
        my_difficultyBox.setOpaque(false);
        dropPanel.setPreferredSize(DROP_PANEL_SIZE);
        dropPanel.setMaximumSize(DROP_PANEL_SIZE);
        return dropPanel;
    }
    private JPanel buildIngredientPanel() {
        JPanel ingredPanel = new JPanel(new GridLayout(1, 0));
        String[][] data = new String[50][4];
        my_table = new JTable(data, INGRED_LABELS);
        my_table.setPreferredScrollableViewportSize(INGRED_TABLE_SIZE);
        my_table.setFillsViewportHeight(true);
        my_table.getModel().addTableModelListener(this);
        //Create the scroll pane and add the table to it.
        WebScrollPane scrollPane = new WebScrollPane(my_table);
        //Add the scroll pane to this panel.
        ingredPanel.add(scrollPane);
        //ingredPanel.setMaximumSize(INGRED_TABLE_SIZE);
        return ingredPanel;
    }
    private JPanel buildDirectionPanel() {
        JPanel directPanel = new JPanel(new BorderLayout());
        
        JLabel directionLabel = new JLabel("Directions (Separate recipe steps by a blank line)");
        directionLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
        my_directionArea = new JTextArea();
        my_directionArea.setLineWrap(true);
        my_directionArea.setWrapStyleWord(true);
            //stepField.setColumns(10);
        directPanel.add(my_directionArea);
        //Scroll Pane for direction text area.
        my_areaScroll = new WebScrollPane(my_directionArea);
        my_areaScroll.getViewport().setOpaque(false);
        my_areaScroll.setOpaque(false);
        //areaScroll.getViewport().setPreferredSize(DIRECT_VPORT_SIZE);
        //areaScroll.getViewport().setMaximumSize(DIRECT_VPORT_SIZE);
        directPanel.add(directionLabel, BorderLayout.NORTH);
        directPanel.add(my_areaScroll, BorderLayout.CENTER);
        directPanel.setPreferredSize(DIRECTION_SIZE);
        
        return directPanel;
    }
    /**
     * Creates two maps: A map from labels to text fields, and a map
     * from labels to warning labels.
     */
    private void createTextMap() {
        my_textMap = new HashMap<String, JTextComponent>();
        my_warnMap = new HashMap<String, JLabel>();
        for (int i = 0; i < LABELS.length; i++) {
            WebTextField textField = new WebTextField();
            JLabel label = new JLabel();
            if (i < 2) {
                textField.setMaximumSize(new Dimension(200, 24));
            } else {
                textField.setMaximumSize(new Dimension(40, 24));
            }
            label.setMaximumSize(new Dimension(100, 18));
            my_textMap.put(LABELS[i], textField);
            my_warnMap.put(LABELS[i], new JLabel());
            my_warnMap.get(LABELS[i]).setForeground(Color.RED);
        }
    }
    /**
     * Handles the logic that checks if a user has input data correctly into
     * the add recipe fields.
     * @return Return true if data was input correctly.
     */
    private boolean validateUser() {
        boolean result = true;
        for (int i = 0; i < LABELS.length - 3; i++) {
            if (my_textMap.get(LABELS[i]).getText().isEmpty()) {
                result = false;
                my_textMap.get(LABELS[i]).setBackground(Color.YELLOW);
                my_warnMap.get(LABELS[i]).setText("Must enter value.");
            } else if (my_textMap.get(LABELS[i]).getBackground() == Color.YELLOW) {
                my_textMap.get(LABELS[i]).setBackground(Color.WHITE);
                my_warnMap.get(LABELS[i]).setText(null);
            }
        }
        for (int i = 2; i < LABELS.length; i++) {
            if (!isInteger(my_textMap.get(LABELS[i]).getText())) {
                my_textMap.get(LABELS[i]).setBackground(Color.YELLOW);
                my_warnMap.get(LABELS[i]).setText("Not valid integer.");
                result = false;
            } else if (my_textMap.get(LABELS[i]).getBackground() == Color.YELLOW) {
                my_textMap.get(LABELS[i]).setBackground(Color.WHITE);
                my_warnMap.get(LABELS[i]).setText(null);
            }
        }
        if (my_directionArea.getText() == null | my_directionArea.getText().isEmpty()) {
            my_directionArea.setBackground(Color.YELLOW);
            result = false;
        } else if (my_directionArea.getBackground() == Color.YELLOW) {
            my_directionArea.setBackground(Color.WHITE);
        }
        boolean noIngredName = true;
        for (int i = 0; i < my_table.getRowCount(); i++) {
            if (my_table.getValueAt(i, 2) == null | (my_table.getValueAt(i, 2) != null && ((String) my_table.getValueAt(i,2)).isEmpty())) {
                for (int j = 0; j < my_table.getColumnCount(); j++) {
                    if (i != 2 && my_table.getValueAt(i, j) != null && !((String) my_table.getValueAt(i,j)).isEmpty()) {
                        noIngredName = false;
                    }
                }
            }
        }
        if (!noIngredName) {
            JOptionPane.showMessageDialog(this, "All ingredients require a name.");
            result = false;
        }
        return result;
    }
    /**
     * Creates and returns the submit recipe button.
     * @return Returns submit recipe button.
     */
    private JPanel makeSubmitHolder() {
        my_submitButton = new JButton("Submit");
        my_submitButton.setBackground(new Color(128, 128, 128));
        my_submitButton.setForeground(Color.WHITE);   
        my_submitButton.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(), 
                                                            BorderFactory.createEmptyBorder(0, 14, 0, 14)));
        my_submitButton.setHorizontalAlignment(SwingConstants.CENTER);
        my_submitButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent the_event) {
                boolean validUser = validateUser();
                if (validUser && my_validFlag) {
                    List<String> newRecipe = createRecipeList();
                    List<List<String>> ingredients = createIngredientList();
                    List<String> steps = createRecipeStepList();
                    
                    if (my_controller.addRecipe(newRecipe, ingredients, steps)) {
                        addRecipeComplete();
                    }                
                } else if (validUser) {
                    my_controller.goAddRecipe();
                }
            }
          });
        JPanel submitHolder = new JPanel();
        submitHolder.add(my_submitButton);
        submitHolder.setOpaque(false);
        return submitHolder;
    }
    private void addEditButton() {
        my_editButton = new JButton("Submit Edit");
        my_editButton.setBackground(new Color(128, 128, 128));
        my_editButton.setForeground(Color.WHITE);   
        my_editButton.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(), 
                                                            BorderFactory.createEmptyBorder(0, 14, 0, 14)));
        my_editButton.setHorizontalAlignment(SwingConstants.CENTER);
        my_editButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent the_event) {
                boolean validUser = validateUser();
                if (validUser && my_validFlag) {
                    List<String> newRecipe = createRecipeList();
                    List<List<String>> ingredients = createIngredientList();
                    List<String> steps = createRecipeStepList();
                    
                    my_recipe = my_controller.editRecipe(my_recipe.getID(), newRecipe, ingredients, steps);
                    editRecipeComplete();    
                } else if (validUser) {
                    my_controller.displayRecipe(my_recipe);
                }
            }
          });
        JPanel editHolder = new JPanel();
        editHolder.add(my_editButton);
        editHolder.setOpaque(false);
        this.add(editHolder, BorderLayout.SOUTH);
    }
    private List<String> createRecipeList() {
        List<String> result = new ArrayList<String>();
        result.add("Title");
        result.add(my_textMap.get(TITLE).getText());
        result.add("author");
        result.add(my_textMap.get(AUTHOR).getText());
        result.add("Prep_Time");
        result.add(my_textMap.get(PREP).getText());
        result.add("Cooking_Time");
        result.add(my_textMap.get(COOK).getText());
        result.add("Serving_Size");
        result.add(my_textMap.get(SERVE).getText());
        result.add("Course");
        result.add((String) my_courseBox.getSelectedItem());
        result.add("Cuisine");
        result.add((String) my_cuisineBox.getSelectedItem());
        result.add("Difficulty");
        result.add((String) my_difficultyBox.getSelectedItem());
        
        return result;
    }
    private List<List<String>> createIngredientList() {
        List<List<String>> result = new ArrayList<List<String>>();
        int[] attrArray = {2, 3, 1, 0}; //2: quantity, 3: unit of measure, 1: name, 0: prep style
        for (int i = 0; i < my_table.getRowCount(); i++) {
            if (my_table.getValueAt(i, 2) != null && !((String) my_table.getValueAt(i, 2)).isEmpty()) {
                List<String> subarray = new ArrayList<String>();
                for (int j = 0; j < attrArray.length; j++) {
                    if (my_table.getValueAt(i, attrArray[j]) != null) {
                        subarray.add((String) my_table.getValueAt(i, attrArray[j]));
                    } else {
                        subarray.add("");
                    }
                }
                result.add(subarray);
            }
        }
        return result;
    }
    private List<String> createRecipeStepList() {
        List<String> result = new ArrayList<String>();
        String allDirections = my_directionArea.getText();
        String lines[] = allDirections.split("[\\r\\n]+");
        for (int i = 0; i < lines.length; i++) {
            result.add(lines[i]);
        }
        return result;
    }
    /**
     * Alters the display to show user registration was successful.
     */
    private void addRecipeComplete() {
        my_titleLabel.setText("Recipe Added to Database");
        for (int i = 0; i < LABELS.length; i++) {
            my_textMap.get(LABELS[i]).setBackground(Color.LIGHT_GRAY);
            my_textMap.get(LABELS[i]).setFocusable(false);
        }
        my_directionArea.setBackground(Color.LIGHT_GRAY);
        my_submitButton.setText("Add Another Recipe");
        my_validFlag = false;
    }
    /**
     * Alters the display to show user edit of recipe was successful.
     */
    private void editRecipeComplete() {
        my_titleLabel.setText("Edit Successful");
        for (int i = 0; i < LABELS.length; i++) {
            my_textMap.get(LABELS[i]).setBackground(Color.LIGHT_GRAY);
            my_textMap.get(LABELS[i]).setFocusable(false);
        }
        my_directionArea.setBackground(Color.LIGHT_GRAY);
        my_editButton.setText("Back to Recipe Display");
        my_validFlag = false;
    }
    private static boolean isInteger(String s) {
        try { 
            Integer.parseInt(s); 
        } catch(NumberFormatException e) { 
            return false; 
        }
        // only got here if we didn't return false
        return true;
    }
    @Override
    public void tableChanged(TableModelEvent e) {
        int row = e.getFirstRow();
        int column = e.getColumn();
        TableModel model = (TableModel)e.getSource();
        String columnName = model.getColumnName(column);
        String data = (String) model.getValueAt(row, column);
        boolean removeData = false;
        if (columnName.equals(INGRED_LABELS[0])) {
            //Decimal Value Check for Quantity.
            if (!isDouble(data)) {
                removeData = true;
            }
        } else if (columnName.equals(INGRED_LABELS[1])) {
            //Unit of Measure Check.
            if (!my_measurements.contains(data)) {
                removeData = true;
            }          
        }
        if (removeData) {
            my_table.getModel().removeTableModelListener(this);
            my_table.setValueAt("", row, column);
            my_table.getModel().addTableModelListener(this);
        }
        
    }
    private boolean isDouble(String the_string) {
        boolean result = false;
        final String Digits     = "(\\p{Digit}+)";
        final String HexDigits  = "(\\p{XDigit}+)";
        // an exponent is 'e' or 'E' followed by an optionally 
        // signed decimal integer.
        final String Exp        = "[eE][+-]?"+Digits;
        final String fpRegex    =
                ("[\\x00-\\x20]*"+  // Optional leading "whitespace"
                        "[+-]?(" + // Optional sign character
                        "NaN|" +           // "NaN" string
                        "Infinity|" +      // "Infinity" string

             // A decimal floating-point string representing a finite positive
             // number without a leading sign has at most five basic pieces:
             // Digits . Digits ExponentPart FloatTypeSuffix
             // 
             // Since this method allows integer-only strings as input
             // in addition to strings of floating-point literals, the
             // two sub-patterns below are simplifications of the grammar
             // productions from the Java Language Specification, 2nd 
             // edition, section 3.10.2.

             // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt
             "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+

             // . Digits ExponentPart_opt FloatTypeSuffix_opt
             "(\\.("+Digits+")("+Exp+")?)|"+

       // Hexadecimal strings
       "((" +
       // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt
       "(0[xX]" + HexDigits + "(\\.)?)|" +

        // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt
        "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +

        ")[pP][+-]?" + Digits + "))" +
        "[fFdD]?))" +
                        "[\\x00-\\x20]*");// Optional trailing "whitespace"

        if (Pattern.matches(fpRegex, the_string)) {
            Double.valueOf(the_string); // Will not throw NumberFormatException
            result = true;
        }
        return result;
    }   
}
